<?PHP
/*
 * $Id: barge.classes.php 139 2009-12-16 15:29:56Z namba0219 $
 * =============================================================================
 * Barge - Web application framework on PHP5
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * =============================================================================
 * For more information, see http://barge.googlecode.com/
 */
namespace barge;

use \Exception;
use \ErrorException;
use \ReflectionMethod;
use \ReflectionClass;
use \ReflectionException;
use \ReflectionProperty;
use \Reflector;

use barge\io\ResourceInputStream;
use barge\io\EchoOutputStream;
use barge\io\FileInputStream;
use barge\io\FileOutputStream;
use barge\logging\LogFactory;
use barge\shell\Shell;
use barge\utils\ArrayList;
use barge\utils\HashMap;
use barge\utils\Config;
use barge\utils\INIConfig;
use barge\utils\XMLConfig;
use barge\utils\ExceptionUtils;
use barge\utils\Timestamp;

/**
 * @author Kenji Namba
 * @version 1.0 2008/08/14
 * @see Exception
 * @since Barge 1.2 2008/08/14
 */
class ModuleNotFoundException extends Exception {}
class ClassNotFouncException extends Exception {}
class ConfigureSystemException extends Exception {}

/**
 * @version 1.0 2008/08/14
 * @author Kenji Namba
 */
class System
{
	/**
	 * @var ClassLoader
	 */
	private static $classLoader;

	/**
	 * @var ErrorHandler
	 */
	private static $errorHandler;

	/**
	 * @var Config
	 */
	private static $config;

	/**
	 * @var string
	 */
	private static $baseDir = '/';

	/**
	 * @var string
	 */
	private static $workDir;

	/**
	 * @var InputStream
	 * @since 1.2.1 2009/03/17
	 */
	private static $stdin;

	/**
	 * @var OutputStream
	 * @since 1.2.1 2009/03/17
	 */
	private static $stdout;

	/**
	 * @var OutputStream
	 * @since 1.2.1 2009/03/17
	 */
	private static $stderr;

	/**
	 * @var long
	 * @since 1.2.1 2009/03/20
	 */
	private static $timeoffset = null;

	/**
	 * @since 1.2.1 2009/03/17
	 * @return InputStream
	 */
	public static function in()
	{

		if (self::$stdin === null) {
			self::$stdin = new FileInputStream('php://stdin');
		}
		return self::$stdin;
	}

	/**
	 * @since 1.2.1 2009/03/17
	 * @return EchoOutputStream
	 */
	public static function out()
	{
		if (self::$stdout === null) {
			$encoding = System::getProperty('System/outputEncoding');
			self::$stdout = new EchoOutputStream($encoding);
		}
		return self::$stdout;
	}

	/**
	 * @since 1.2.1 2009/03/17
	 * @return OutputStream
	 */
	public static function err()
	{
		if (self::$stderr === null) {
			self::$stderr = new FileOutputStream('php://stderr');
		}
		return self::$stderr;
	}

	/**
	 * 既定のクラスローダを返します
	 * @return ClassLoader
	 * @since 1.0 2008/09/14
	 */
	public static function getClassLoader()
	{
		if (self::$classLoader === null) {
			// Note : この部分のクラスは既に解決済みでなければならない
			self::$classLoader = new ClassLoader();
		}
		return self::$classLoader;
	}

	/**
	 * @return ErrorHandler
	 * @since 1.0 2008/09/14
	 */
	public static function getErrorHandler()
	{
		if (self::$errorHandler === null) {
			self::$errorHandler = new DefaultErrorHandler();
		}
		return self::$errorHandler;
	}

	/**
	 * @param ErrorHandler $errorHandler
	 * @since 1.0 2008/09/14
	 */
	public static function setErrorHandler(ErrorHandler $errorHandler)
	{
		self::$errorHandler = $errorHandler;
	}

	/**
	 * @param int $status 終了コード
	 * @since 1.0 2008/09/14
	 */
	public static function end($status = 0)
	{
		exit($status);
	}

	/**
	 * システムのベースディレクトリーを指定する
	 *
	 * <pre>
	 * System::setBaseDir('../webapps');
	 * System::setBaseDir('/var/www/webapps');
	 * </pre>
	 *
	 * @param string $dir クラスパスからの相対パスまたは絶対パスを指定します
	 * @since 1.0 2008/09/14
	 */
	public static function setBaseDir($dir)
	{
		$realpath = realpath($dir);
		if ($realpath === false) {
			$message = "ベースディレクトリー '{$dir}' は存在しません。";
			throw new ConfigureSystemException($message);
		}
		self::$baseDir = str_replace('\\', '/', $realpath);
	}

	/**
	 * システムベースディレクトリからの相対パスを絶対パスに変換します
	 *
	 * @param string $path システムベースディレクトリからの相対パス
	 * @return string
	 * @since 1.0 2008/09/14
	 */
	public static function getRealPath($path)
	{
		return (substr(self::$baseDir, -1, 1) === '/' ? '' : self::$baseDir).'/'.($path === '/' ? '' : $path);
	}

	/**
	 * @return string
	 * @since 1.0 2008/09/14
	 */
	public static function getBaseDir()
	{
		return self::$baseDir;
	}

	/**
	 * @param string $path
	 * @param mixed $value
	 * @since 1.0 2008/09/14
	 */
	public static function setProperty($path, $value)
	{
		if (self::$config === null) self::$config = new Config();
		$reflection = new ReflectionMethod('barge\utils\Config', 'setProperty');
		$reflection->invoke(self::$config, $path, $value);
	}

	/**
	 * プロパティーファイルをプロパティーテーブルにロードします。プロパティーファイルは、
	 * iniまたはxml形式に対応しています。
	 *
	 * @param string $filepath
	 * @param string $type 省略可能、省略された場合は拡張子から判断する ini|xml
	 * @since 1.0 2008/09/14
	 */
	public static function loadProperty($filepath, $type = null)
	{
		if (!self::isAbsolute($filepath)) {
			$filepath = System::getBaseDir().'/'.$filepath;
		}
		if ($type === null && ($p = strrpos($filepath, '.')) !== false) {
			$type = strtolower(substr($filepath, $p + 1));
		}
		$tmp = null;
		if ($type === 'xml') {
			$tmp = XMLConfig::load($filepath, dirname($filepath));
		} else if ($type === 'ini') {
			$tmp = INIConfig::load($filepath, dirname($filepath));
		} else {
			$message = "Unsupported property type '{$type}'.";
			throw new ConfigureSystemException($message);
		}
		if (self::$config === null) {
			self::$config = $tmp;
		} else {
			self::$config->merge($tmp);
		}
	}

	/**
	 * システムワークディレクトリを設定します
	 * @param string $path
	 * @since 1.0 2008/09/14
	 */
	public static function setWorkDir($path)
	{
		self::$workDir = $path;
	}

	/**
	 * ワークディレクトリを返します
	 * @return string
	 * @since 1.0 2008/09/14
	 */
	public static function getWorkDir()
	{
		if (self::$workDir === null) {
			$workdir = null;
			if (function_exists('sys_get_temp_dir')) {
				$workdir = sys_get_temp_dir();
			} else if (!empty($_ENV['TEMP'])) {
				$workdir = realpath($_ENV['TEMP']);
			} else if (!empty($_ENV['TMP'])) {
				$workdir = realpath($_ENV['TMP']);
			} else {
				$message = "ワークディレクトリを見つけることができません。";
				throw new Exception($message);
			}
			self::$workDir = $workdir;
		}
		return self::$workDir;
	}

	/**
	 * システムプロパティーを取得します
	 *
	 * @param string $path キーまたは設定値のパス、パスはスラッシュで区切ります
	 * @param mixed $default 値が取得できない場合に使用する値
	 * {@return mixed}
	 * @since 1.0 2008/09/14
	 */
	public static function getProperty($path, $default = null)
	{
		if (self::$config === null) {
			return null;
		}
		return self::$config->getProperty($path, $default);
	}

	/**
	 * @param string
	 * @return bool
	 * @since 1.0 2008/10/18
	 */
	public static function isAbsolute($path)
	{
		return ($path[0] === '/' || $path[1] === ':');
	}

	/**
	 * @return Timestamp
	 * @since 1.2.2 2009/04/07
	 */
	public static function currentTime()
	{
		$offset = self::getTimeOffset();
		$dt = new Timestamp($offset + time());
		//$dt->setTimestamp($offset + time());
		return $dt;
	}

	/**
	 * @return float 例）1237479963.663
	 * @since 1.2.2 2009/04/07
	 */
	public static function currentMicroTime()
	{
		$offset = self::getTimeOffset();
		// TODO : Date control.
		return microtime(true);
	}

	/**
	 * @return int
	 */
	private static function getTimeOffset()
	{
		if (self::$timeoffset === null) {
			$datepath = System::getWorkDir() . '/date.tmp';
			if (file_exists($datepath)) {
				self::$timeoffset = intval(file_get_contents($datepath));
			} else {
				self::$timeoffset = 0;
			}
		}
		return self::$timeoffset;
	}

	/**
	 * @param DateTime $datetime
	 * @since 1.2.2 2009/04/07
	 */
	public static function setCurrentTime(DateTime $datetime)
	{
		$now = new DateTime();
		$offset = intval($datetime->format("U")) - intval($now->format("U"));
		self::setTimeOffset($offset);
	}

	/**
	 * @param int $offset
	 * @since 1.2.2 2009/04/07
	 */
	public static function setTimeOffset($offset)
	{
		$datepath = System::getWorkDir() . '/date.tmp';
		if (is_writable($datepath)) {
			file_put_contents($datepath, $offset);
		}
		self::$timeoffset = $offset;
	}

	/**
	 *
	 * @param mixed $value
	 * @since 1.2.2 2009/04/07
	 */
	public static function isNotEmpty($value)
	{
		return !self::isEmpty($value);
	}

	/**
	 *
	 * @param mixed $value
	 * @since 1.2.2 2009/04/07
	 * @see #empty
	 */
	public static function isEmpty($value)
	{
		return empty($value);
	}

	/**
	 * パラメータの先頭より最初に空(empty)でない要素を返す
	 *
	 * @param mixed $value
	 * @since 1.2.2 2009/04/07
	 */
	public static function firstNotEmpty(/*...*/)
	{
		foreach (func_get_args() as $arg) {
			if (self::isNotEmpty($arg)) {
				return $arg;
			}
		}
		return null;
	}

	/**
	 * PHPバージョンを返します
	 * @return string
	 */
	public static function getPHPVersion()
	{
		return PHP_VERSION;
	}

	/**
	 * オペレーションシステムを返します
	 * @return string
	 */
	public static function getPlatform()
	{
		return PHP_OS;
	}

	/**
	 * 利用可能なバイト数を返します
	 *
	 * @return flaot バイト数
	 * @since 1.2 2008/09/11
	 */
	public static function getFreeDiskspace()
	{
		return disk_free_space("/");
	}

	/**
	 * ディスク全体のバイト数を返します
	 *
	 * @return flaot バイト数
	 * @since 1.2 2008/09/11
	 */
	public static function getTotalDiskspace()
	{
		return disk_total_space("/");
	}

	/**
	 * シャットダウンハンドラーを登録する
	 *
	 * @parma ShutdownHandler|string $handler {@link barge\ShutdownHandler}の実装クラス名またはインスタンス
	 */
	public static function registerShutdownHandler($handler, ArrayList $parameters = null)
	{
		register_shutdown_function(array($handler, 'shutdown'),
			$parameters != null ? $parameters->toArray() : array());
	}

	/**
	 * 現在有効なモジュールの一覧を返します
	 * @return ArrayList
	 */
	public static function listExtensions()
	{
		return new ArrayList(get_loaded_extensions());
	}

	/**
	 * 指定されたモジュールがインストールされているかどうかを調べる
	 * @param string $moduleName モジュール名、例）"mbstring", "pgsql"
	 * @return bool
	 */
	public static function existsExtension($moduleName)
	{
		return extension_loaded($moduleName);
	}

	/**
	 * @return Shell
	 */
	public static function getShell()
	{
		$shell = new Shell();
		if (strtoupper(substr(self::getPlatform(), 0, 3)) === 'WIN') {
			$shell->setInputEncoding('shift_jis');
			$shell->setOutputEncoding('shift_jis');
		}
		return $shell;
	}
}

/**
 * エラーハンドリングのためのインターフェース
 * @since 1.0 2009/04/11
 */
interface ErrorHandler
{
	/**
	 * @param int $errno
	 * @param string $errstr
	 * @param string $errfile
	 * @param int $errline
	 * @param array $contxt
	 */
	public function uncoughtableError($errno, $errstr, $errfile = null, $errline = null, array $contxt = null);

	/**
	 * @param Exception $e
	 */
	public function uncoughtableException(Exception $e);
}

/**
 * @since 1.0 2009/04/11
 * @see ErrorHandler
 */
class DefaultErrorHandler implements ErrorHandler
{
	/**
	 * @var array エラー表示名
	 */
	private static $names = array(
			E_ERROR => 'ERROR',
			E_WARNING => 'WARNING',
			E_PARSE => 'PARSE',
			E_NOTICE => 'NOTICE',
			E_CORE_ERROR => 'CORE_ERROR',
			E_CORE_WARNING => 'CORE_WARNING',
			E_COMPILE_ERROR => 'COMPILE_ERROR',
			E_COMPILE_WARNING => 'COMPILE_WARNING',
			E_USER_ERROR => 'USER_ERROR',
			E_USER_WARNING => 'USER_WARNING',
			E_USER_NOTICE => 'USER_NOTICE',
			E_STRICT => 'STRICT',
			E_RECOVERABLE_ERROR => 'RECOVERABLE_ERROR',
			E_DEPRECATED => 'DEPRECATED',
			E_USER_DEPRECATED => 'USER_DEPRECATED',
	//		E_ALL => 'E_ALL',
			);


	/**
	 * @param int $errno
	 * @param string $errstr
	 * @param string $errfile
	 * @param int $errline
	 * @param array $contxt
	 * @see ErrorHandler#uncoughtableError
	 */
	public function uncoughtableException(Exception $e)
	{
		try {
			// Note : ブラウザで表示できて、コンソールアプリでも影響がない方法
			header('Content-type: text/plain; charset=UTF-8');
			System::out()->write(ExceptionUtils::getTraceAsString($e));
			// TODO : System を barge\logging に依存させない実装
			$log = LogFactory::getLogger();
	    	$log->warn($e->getMessage());
		} catch (Exception $e) {}
	}

	/**
	 * @param Exception $e
	 */
	public function uncoughtableError($errno, $errstr, $errfile = null, $errline = null, array $contxt = null)
	{
		// Clear output buffer.
		while (@ob_end_clean());

		// ユーザーエラーの場合は例外に変換できるが、致命的な例外の場合これ以降の処理は中断される
		$name = (isset(self::$names[$errno]) ? self::$names[$errno] : 'UNKNOWN_ERROR');
		$message = "{$name} : {$errstr} in {$errfile}({$errline})";
		$e = new ErrorException($message, 0, $errno, $errfile, $errline);
		$this->uncoughtableException($e);
		die();
	}
}

/**
 * ファイルシステム上のパッケージ構造を持つクラスファイルを読み込むメソッドを提供します。
 *
 * @author Kenji Namba
 * @version 1.1 2009/03/05
 * @since Barge 1.1  2006/08/14
 */
class ClassLoader
{
	/**
	 * @var array
	 */
	private static $classpaths = array();

	/**
	 * @var array
	 */
	private static $includes = array();

	/**
	 * @var array
	 */
	private static $foundModules = array();

	/**
	 * @since 1.4 2009/11/05
	 * @var array
	 */
	private static $classfiles = array();

	/**
	 * モジュールを検索するディレクトリパスを追加します
	 *
	 * @param string $classpath システムルートからの相対パスまたは、絶対パスで指定します
	 * @since 1.0 2006/10/16
	 */
	public function addIncludePath($classpath)
	{
		$realpath = null;
		if (System::isAbsolute($classpath)) {
			$realpath = str_replace('\\', '/', realpath($classpath));
		} else {
			$realpath = str_replace('\\', '/', realpath(System::getBaseDir().'/'.$classpath));
		}

		if (in_array($realpath, self::$includes)) return;
		set_include_path(get_include_path() . PATH_SEPARATOR . $realpath);
		self::$includes[] = $realpath;
	}

	/**
	 * 指定クラスを読み込むパスを指定します。PHP5.2以前の個別のクラスをクラスローディングするために
	 * 使用します。
	 *
	 * @param string $class クラス名
	 * @param string $classpath インクルードするファイル、絶対パスまたはシステムベースディレクトリ
	 * 			からの相対パスを指定できます。
	 * @since 1.0 2006/10/16
	 */
	public function addClassIncludePath($class, $classpath)
	{
		$realpath = null;
		if (System::isAbsolute($classpath)) {
			if (($path = realpath($classpath)) !== false) {
				$realpath = str_replace('\\', '/', $path);
			} else {
				$message = "Include path not found '$classpath'";
				throw new Exception($message);
			}
		} else {
			$classpath = System::getBaseDir().'/'.$classpath;
			if (($path = realpath($classpath)) === false) {
				$message = "Include path not found '$classpath'";
				throw new Exception($message);
			}
			$realpath = str_replace('\\', '/', $path);
		}
		self::$classpaths[$class] = $realpath;
	}

	/**
	 * クラスパス設定ファイルを読み込む
	 *
	 * @param string $confpath インクルードファイル名
	 * @since 1.1 2009/03/05
	 */
	public function addClassIncludeConfig($confpath)
	{
		$realpath = null;
		if (System::isAbsolute($confpath)) {
			if (($path = realpath($confpath)) !== false) {
				$realpath = str_replace('\\', '/', $path);
			} else {
				$message = "Include path not found '$confpath'";
				throw new Exception($message);
			}
		} else {
			if (($path = realpath($confpath)) !== false) {
				$realpath = str_replace('\\', '/', $path);
			} else {
				$message = "Include path not found '$confpath'";
				throw new Exception($message);
			}
		}
		array_merge(self::$confpaths, INIConfig::load()->toArray($confpath));
	}

	/**
	 * モジュールをインクルードします。この処理は外部のライブラリーなどで使用します
	 *
	 * @param string $module_path
	 * @since 1.0 2006/10/16
	 * @throws ModuleNotFoundException
	 */
	public function includeModule($module_path)
	{
		if (empty($module_path) || !$this->existsModule($module_path)) {
			$message = "指定されたモジュールが見つからないか、読み込むことができません。 '{$module_path}' in [" . get_include_path() . "]";
			throw new ModuleNotFoundException($message);
		}

		require_once($module_path);
	}

	/**
	 * バイナリーファイルからオブジェクトを復元(unserialize)します
	 * @param $file_path
	 * @since 1.0 2006/10/16
	 */
	public function restoreInstance($file_path)
	{
		return unserialize(File::getContents($file_path));
	}

	/**
	 * バイナリーファイルへオブジェクトを格納します
	 * @param string $file_path
	 * @param object $object
	 * @since 1.0 2006/10/16
	 */
	public function storeInstance($file_path, $object)
	{
		File::putContents(serialize($object));
	}

	/**
	 * クラス名からクラスのインスタンスを生成します。クラス定義が存在しない場合はクラスパスか
	 * らモジュールファイルを検索し自動的にインクルードします。
	 *
	 * @param string $className クラス名、クラスがパッケージを持つ場合、バックスラッシュ形式のフルクラス名をしています。
	 * @param ArrayList $args コンストラクタへ渡すパラメータの一覧
	 * @throws ClassNotFouncException クラスが存在しない場合にスローされる
	 * @since 1.0 2006/10/16
	 */
	public function newInstance($className, ArrayList $args = null)
	{
		// スラッシュを\に変換
		$className = str_replace('/', '\\', $className);
		if (!$this->existsClass($className)) {
			$message = "クラス {$className} のインスタンスを生成できません。";
			throw new ClassNotFouncException($message);
		}
		$refrection = new ReflectionClass($className);
		return ($args !== null) ?
			$refrection->newInstanceArgs($args->toArray()) :
			$refrection->newInstance();
	}

//	/**
//	 * @param string class
//	 * @since 1.2 2008/10/17
//	 */
//	public function findClass($class)
//	{
//		class_exists($class, true);
//		return $class;
//	}

	/**
	 * @param string $classname
	 * @param bool $autoload = true
	 * @return bool
	 * @since 1.0 2008/10/17
	 */
	public function existsClass($classname, $autoload = true)
	{
		return class_exists($classname, $autoload);
	}

	/**
	 * @param string $interface
	 * @param bool $autoload = true
	 * @return bool
	 * @since 1.4 2009/11/13
	 */
	public function existsInterface($interface, $autoload = true)
	{
		return interface_exists($interface, $autoload);
	}

	/**
	 * 指定されたクラスファイルがクラスパス上に存在するかどうかテスト
	 * します。このメソッドは、テスト結果をキャッシュし次回から高速に結果を返します。
	 *
	 * @param string $module モジュールパス
	 * @return bool
	 * @since 1.0 2008/10/17
	 */
	public function existsModule($module)
	{
		if (isset(self::$foundModules[$module]))
			return true;

		// 絶対パス指定で検索する
		if ($module[0] === '/' || $module[1] === ':') {
			$ret = file_exists($module);
			if ($ret)
				self::$foundModules[$module] = true;
			return $ret;
		}

		// ファイル検索にクラスパスを使用する
		foreach (self::$includes as $includeDir) {
//echo ($includeDir.'/'.$module.'-'.(file_exists($includeDir.'/'.$module) ? 'yes' : 'no'). '<br>');
			if (file_exists($includeDir.'/'.$module)) {
				self::$foundModules[$module] = true;
				return true;
			}
		}
		return false;
	}

	/**
	 * @param string $className
	 */
	public function loadClass($className)
	{
		$classpath = null;
		if (isset(self::$classpaths[$className])) {
			$classpath = self::$classpaths[$className];
		} else {
			// Note : _のリプレースを行うと Pear スタイルのクラス名でもパッケージ構造を扱えるが、
			//			クラス名に _ が含まれるクラス（e.g:_Bean）をうまく読み込むことができない
			$classpath = str_replace(array('\\'), '/', $className).'.class.php';
//			$classpath = str_replace(array('\\', '_'), '/', $className).'.class.php';
		}

		if ($this->existsModule($classpath)) {
			$this->includeModule($classpath);
//			self::$classfiles[$className] = $incluespath;
		} else {
			$classdir = dirname($classpath);
			$incluespath = ($classdir !== '.' ? dirname($classpath).'/'.
				str_replace('/', '-',dirname($classpath)) : '').'.classes.php';

			if ($this->existsModule($incluespath)) {
				$this->includeModule($incluespath);
//				self::$classfiles[$className] = $incluespath;
			} else {
				$message = "クラス '{$className}' が見つかりません。'{$classpath}'または、'{$incluespath}'が必要です。";
				throw new ModuleNotFoundException($message);
			}
		}
	}
}

/**
 * アノテーションのためのインターフェースクラス
 * @since 1.4 2009/11/03
 */
class AnnotationProxy
{
	/**
	 * クラス名
	 */
	private $annotationClass;

	/**
	 * @array
	 */
	private $properties;

	/**
	 * @param $annotationClass
	 * @param array $properties
	 */
	public function __construct($annotationClass, array $properties)
	{
		$this->annotationClass = $annotationClass;
		$this->properties = $properties;
	}
	public function __call($name, $arguments)
	{
		$reflectionClass = new ReflectionClass($this->annotationClass);
		if ($reflectionClass->hasMethod($name)) {
			return isset($this->properties[$name]) ? $this->properties[$name] : null;
		} else {
			$message = "Undefined method '{$name}' : '{$this->annotationClass}'";
			throw new ReflectionException($message);
		}
	}
	public function getAnnotationClass()
	{
		return $this->annotationClass;
	}
}

/**
 * @since 1.4 2009/11/03
 * @see Exception
 */
class MissingAnnotationException extends Exception {}

/**
 * @since 1.4 2009/11/03
 */
interface AnnotationDescriptor
{
	public function getPropertyAnnotations($name);
	public function getMethodAnnotations($name);
	public function getClassAnnotations();
}

/**
 * 拡張アノテーションを解析するためのリフレクタ、拡張アノテーションは、
 * @~で始まる、存在するクラスによって定義します。

 * <pre>
 * @~クラス名(キー1:値1, キー2:値2 ... ）
 * </pre>
 *
 * @since 1.4 2009/11/03
 * @see Reflector
 */
class ReflectionAnnotation implements Reflector
{
	/**
	 * @var array
	 */
	private $description;

	/**
	 * クラス名
	 * @var string
	 */
	private $className;

	/**
	 * Constructor
	 * @param string|object $className object of class name
	 */
	public function __construct($className)
	{
		$this->className = is_object($className) ? get_class($className) : $className;

		$ref = new ReflectionClass($className);
		$descriptor = $ref->getNamespaceName().'/descriptor/'. $ref->getShortName().'Descriptor';
		$basedir = System::getWorkDir() . '/.classes/';
		$workdir = $basedir.'/'.dirname($descriptor);
		if (!file_exists($workdir)) {
			@mkdir($workdir, null, true);
		}
		$classLoader = System::getClassLoader();
		$classLoader->addIncludePath($basedir);

		$path = $basedir . '/' . $descriptor. '.class.php';
		if (!file_exists($path) || filemtime($path) !== filemtime($ref->getFileName())) {
			$annotasions = $this->parseAnnotations($className);
			$php = $this->createDescriptor($ref, $annotasions);
			file_put_contents($path, $php);
			@touch($path, filemtime($ref->getFileName()));
		}
		$this->description = $classLoader->newInstance($descriptor);
	}

	/**
	 * @param ReflectionClass $ref
	 * @param array $annotasionList
	 */
	private function createDescriptor(ReflectionClass $ref, array $annotasionList)
	{
		$php = "<?php\n";
		$php .= 'namespace '.$ref->getNamespaceName().'\\descriptor;'."\n";
		$php .= 'class '.$ref->getShortName().'Descriptor implements \barge\AnnotationDescriptor {'."\n";
		$php .= 'private static $c;';
		$php .= 'private static $p;';
		$php .= 'private static $m;';
		$php .= "public function getClassAnnotations() {\n";
		$php .= 'if (self::$c === null) $c = array(';
		foreach ($annotasionList['class'] as $annotation) {
			$php .= 'new \barge\AnnotationProxy(\''.$annotation['class'].'\','
				.$this->toArrayExpression($annotation['properties']).')';
		}
		$php .= ");\n";
		$php .= 'return $c;';
		$php .= "}\n";
		$php .= 'public function getPropertyAnnotations($name) {';
		$php .= 'if (self::$p === null) $p = array(';
		$first = true;
		foreach ($annotasionList['property'] as $key => $annotations) {
			if (!$first) $php .= ',';
			$first = false;
			$php .= "'".$key."'=>array(\n";
			$first2 = true;
			foreach ($annotations as $annotation) {
				if (!$first2) $php .= ',';
				$first2 = false;
				$php .= 'new \barge\AnnotationProxy(\''.$annotation['class'].'\','
					.$this->toArrayExpression($annotation['properties']).')';
			}
			$php .= ")\n";
		}
		$php .= ");\n";
		$php .= 'return isset($p[$name]) ? $p[$name] : array();';
		$php .= "}\n";
		$php .= 'public function getMethodAnnotations($name) {';
		$php .= 'if (self::$m === null) $m = array(';
		$first = true;
		foreach ($annotasionList['method'] as $key => $annotations) {
			if (!$first) $php .= ',';
			$first = false;
			$php .= "'".$key."'=>array(\n";
			$first2 = true;
			foreach ($annotations as $annotation) {
				if (!$first2) $php .= ',';
				$first2 = false;
				$php .= 'new \barge\AnnotationProxy(\''.$annotation['class'].'\','
					.$this->toArrayExpression($annotation['properties']).')';
			}
			$php .= ")\n";
		}
		$php .= ");\n";
		$php .= 'return isset($m[$name]) ? $m[$name] : array();';
		$php .= "}\n";
		$php .= "}\n";
		return $php ;
	}

	/**
	 * @param string $anotationClass
	 * @return object
	 */
	public function getClassAnnotation($anotationClass)
	{
		$list = $this->getClassAnnotations($anotationClass);
		return ($list->hasElement() ? $list->get(0) : null);
	}

	/**
	 * @param string $propertyName
	 * @param string $anotationClass
	 * {@return object}
	 */
	public function getPropertyAnnotation($propertyName, $anotationClass)
	{
		$list = $this->getPropertyAnnotations($propertyName, $anotationClass);
		return ($list->hasElement() ? $list->get(0) : null);
	}

	/**
	 * @param $classAnnotations
	 * @param $testClass
	 */
	private static function filterCompatile($classAnnotations, $testClass = null)
	{
		if ($testClass === null)
			return $classAnnotations;
		$list = new ArrayList();
		foreach ($classAnnotations as $anotaion) {
			if ($anotaion->getAnnotationClass() === $testClass) {
				$list->add($anotaion);
			} else {
				$c = new ReflectionClass($anotaion->getAnnotationClass());
				if ($c->implementsInterface($testClass)) {
					$list->add($anotaion);
				}
			}
		}
		return $list;
	}

	/**
	 * @param string $anotationClass
	 * @return ArrayList
	 */
	public function getClassAnnotations($anotationClass = null)
	{
		$annotations = $this->description->getClassAnnotations();
		return self::filterCompatile($annotations, $anotationClass);
	}

	/**
	 * @param string $propertyName
	 * @param string $anotationClass
	 * @return ArrayList
	 */
	public function getPropertyAnnotations($propertyName, $anotationClass)
	{
		$annotations = $this->description->getPropertyAnnotations($propertyName);
		return self::filterCompatile($annotations, $anotationClass);
	}

	/**
	 * @param string $methodName
	 * @param string $anotationClass
	 * @return ArrayList
	 */
	public function getMethodAnnotations($methodName, $anotationClass)
	{
		$annotations = $this->description->getMethodAnnotations($methodName);
		return self::filterCompatile($annotations, $anotationClass);
	}

	/**
	 * @param string $comment
	 * @return array
	 */
	private function parseAnnotations($className)
	{
		$ref = new ReflectionClass($className);
		$classAno = $this->parseComment($ref->getDocComment());
		$properties = $ref->getProperties();
		$property_ano = array();
		foreach ($properties as $property) {	/* @var $property ReflectionProperty */
			$property_ano[$property->getName()] = $this->parseComment($property->getDocComment());
		}
		$methodAnnos = array();
		$methods = $ref->getMethods();
		foreach ($methods as $method) {	/* @var $method ReflectionMethod */
			$methodAnnos[$method->getName()] = $this->parseComment($method->getDocComment());
		}
		return array('class'=>$classAno, 'property'=>$property_ano, 'method'=>$methodAnnos);
	}

	/**
	 * @param array $array
	 * @return string
	 */
	private function toArrayExpression($array)
	{
		$exp = 'array(';
		$first = true;
		foreach ($array as $key => $value) {
			if (!$first) $exp.= ',';
			$first = false;
			$exp .= "'".$key."'=>";
			if (is_int($value)) {
				$exp .=$value;
			} else if (is_bool($value)) {
				$exp .=$value ? 'true' : 'false';
			} else {
				$exp .="'".$value."'";
			}
		}
		$exp .= ')';
		return $exp;
	}

	/**
	 * @param string $comment phpdoc comment
	 * @return array
	 */
	private function parseComment($comment)
	{
		$spaces = array("\n", ' ', "\t", "\r");
		$quotes = array("'", '"');

		$list = array();
		$lines = explode("\n", $comment);
		foreach ($lines as $line) {
			$matches = array();
			$p = 0;
			if (($p = strpos($line, '@~')) === false ) continue;
			$value = substr($line, $p + 2);
			$start  = strpos($value, '(');
			$end = stripos($value, ')');
			if ($start === false || $end === false) {
				$message = "アノテーションを解析できません。 {$line}";
				throw new MissingAnnotationException($message);
			}
			$class = trim(substr($value, 0, $start));
			$params = trim(substr($value, $start + 1, $end - $start - 1));
			$classLoader = System::getClassLoader();
			if (! $classLoader->existsInterface($class)) {
				$message = "アノテーションインターフェース '{$class}' が存在しません。";
				throw new MissingAnnotationException($message);
			}
			// TODO : ネストしたプロパティーを解析
			$i = 0;
			$len = strlen($params);
			$properties = array();
			while ($i < $len) {
				$key = null;
				while ($i < $len && $params[$i] !== '=') {
					$key .= $params[$i];
					$i++;
				}
				$i++;
				while ($i < $len && in_array($params[$i], $spaces)) $i++;
				$quote = in_array($params[$i], $quotes) ? $params[$i] : null;
				if ($quote != null) $i++;
				$value = null;
				$bc = null;
				while ($i < $len && $params[$i] !== ',') {
					if ($params[$i] === $quote) {
						if ($bc === '\\') {
							$value = substr($value, 0, -1);
						} else {
							break;
						}
					}
					$value .= $params[$i];
					$bc = $params[$i];
					$i++;
				}

				if ($quote !== null) {
					$value = $value;
				} else if (strcasecmp($value, 'true') === 0 || strcasecmp($value, 'false') === 0) {
					$value = (strcasecmp($value, 'true') === 0);
				} else {
					$value = intval($value);
				}
				$properties[trim($key)] = $value;
				while ($i < $len && $params[$i] !== ',') $i++;
				$i++;
			}
			$list[] = array('class'=>$class, 'properties'=> $properties);
		}
		return $list;
	}

	/**
	 * @see Reflector::export() override
	 */
	public static function export()
	{
		// TODO:
	}

	/**
	 * @see Reflector::__toString()
	 */
	public function __toString()
	{
		$ref = new ReflectionClass($this->className);
		return $ref->__toString();
	}
}

/**
 * シャットダウンハンドラインターフェース
 *
 * @author Kenji Namba
 * @version 1.0
 * @since Barge 1.3 2009/04/16
 */
interface ShutdownHandler
{
	/**
	 * @param $args
	 */
	public function shutdown($param);
}


